/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package dale2507.gates.gate;

import dale2507.gates.data.GateLoader;
import dale2507.gates.data.Settings;
import dale2507.gates.events.GateStatusChangeEvent;
import dale2507.gates.exceptions.VerificationException;
import dale2507.gates.misc.DirectionSensitiveLocation;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Objects;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.vehicle.VehicleMoveEvent;

/**
 *
 * @author Dale
 */
public class Ring extends IRing {
    
    //<editor-fold defaultstate="collapsed" desc="Static methods for getting ring blocks">
    public static Block[] getIntendedRingBlocks(Location center) {
        return getIntendedRingBlocks(new DirectionSensitiveLocation(center));
    }
    
    public static Block[] getIntendedRingBlocks(DirectionSensitiveLocation center) {
        ArrayList<Block> blocks = new ArrayList<>();
        
        // Get top, bottom
        for (int y=-3; y<=3; y+=6) {
            for (int x=-2; x<=2; x++) {
                blocks.add(center.getRelative(x, y, 0).getBlock());
            }
        }
        
        // Get sides
        for (int x=-3; x<=3; x+=6) {
            for (int y=-2; y<=2; y++) {
                blocks.add(center.getRelative(x, y, 0).getBlock());
            }
        }
        
        // Get corners
        for (int x=-2; x<=2; x+=4) {
            for (int y=-2; y<=2; y+=4) {
                blocks.add(center.getRelative(x, y, 0).getBlock());
            }
        }
        
        return blocks.toArray(new Block[0]);
    }
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Variables">
    private DirectionSensitiveLocation center;
    private Block[] ringBlocks;
    private Block[] innerBlocks;
    private HashMap<Integer, Block[]> chevrons = new HashMap<>();
    private Vortex vortex;
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Constructors">
    public Ring(DataInputStream dis) throws Exception {
        this(GateLoader.readLocation(dis), Settings.getInstance().creation.fixOnLoad());
    }
    
    public Ring(Location center) throws VerificationException {
        this(center, false);
    }
    
    public Ring(Location center, boolean fix) throws VerificationException {
        this(new DirectionSensitiveLocation(center), fix);
    }
    
    public Ring(DirectionSensitiveLocation center) throws VerificationException {
        this(center, false);
    }
    
    public Ring(DirectionSensitiveLocation center, boolean fix) throws VerificationException {
        this.center = center;
        this.ringBlocks = Ring.getIntendedRingBlocks(center);
        Material m = Settings.getInstance().creation.getGateMaterial();
        
        // Check that ring is made of correct material
        for (Block b : this.ringBlocks) {
            if (!b.getType().equals(m)) {
                if (fix) {
                    b.setType(Settings.getInstance().creation.getGateMaterial());
                } else {
                    throw new VerificationException("Ring is not the correct shape or material");
                }
            }
        }
        
        // Get inner blocks
        ArrayList<Block> ib = new ArrayList<>();
        for (int x=-2; x<=2; x++) {
            for (int y=-2; y<=2; y++) {
                if (!(Math.abs(x) == Math.abs(y) && Math.abs(x) == 2)) {
                    ib.add(center.getRelative(x, y, 0).getBlock());
                }
            }
        }
        this.innerBlocks = ib.toArray(new Block[0]);
        
        // Get chevrons
        addChevron(0, 2, 3);
        addChevron(1, 3, 1);
        addChevron(2, 3, -1);
        addChevron(3, 2, -3);
        addChevron(4, -2, -3);
        addChevron(5, -3, -1);
        addChevron(6, -3, 1);
        addChevron(7, -2, 3);
        addChevron(8, 0, 3);
    }
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Initialisation method">
    @Override
    /**
     * Initialisation method. Overriden to find the correct gate direction on load.
     */
    void init() {
        if (this.center.getPitch()%180 != 0) {
            // Gate is horizontal, rotate to face up/down
            int ringY = this.center.getBlockY();
            int displayY = this.getGate().getDisplay().getBlock().getY();
            if (ringY < displayY) {
                this.center.setPitch(270f);
            } else {
                this.center.setPitch(90f);
            }
        } else if (this.center.getYaw()%180 != 0) {
            // Locate sign on x axis
            int ringX = this.center.getBlockX();
            int displayX = this.getGate().getDisplay().getBlock().getX();
            if (ringX < displayX) {
                this.center.setYaw(270f);
            } else {
                this.center.setYaw(90f);
            }
        } else {
            // Locate sign on z axis
            int ringZ = this.center.getBlockZ();
            int displayZ = this.getGate().getDisplay().getBlock().getZ();
            if (ringZ > displayZ) {
                this.center.setYaw(180f);
            } else {
                this.center.setYaw(0f);
            }
        }
        
        // Run vortex once to clean up any left on unload
        if (Settings.getInstance().connection.enableVortex())
            new Vortex((DirectionSensitiveLocation) this.getExit());
        
        super.init();
    }
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Chevron Methods">
    private void addChevron(int chevron, int x, int y) {
        Block[] blocks = new Block[2];
        if (this.getExit().getPitch() != 0) {
            blocks[0] = this.center.getRelative(x, y, 1).getBlock();
            if (blocks[0].getRelative(BlockFace.DOWN).isEmpty()) blocks[0] = null;
            blocks[1] = this.center.getRelative(x, y, -1).getBlock();
            if (blocks[1].getRelative(BlockFace.DOWN).isEmpty()) blocks[1] = null;
        } else {
            blocks[0] = this.center.getRelative(x, y, 1).getBlock();
            blocks[1] = this.center.getRelative(x, y, -1).getBlock();
        }
        this.chevrons.put(chevron, blocks);
    }
    
    @Override
    public void setChevron(int chevron, boolean on) {
        Block[] blocks = this.chevrons.get(chevron);
        if (blocks != null) {
            for (Block b : blocks) {
                if (b != null) {
                    if (on && (b.getType() == null || b.getType().equals(Material.AIR) || b.getType().equals(Material.SNOW))) {
                        if (this.getChevronMaterial() != null) {
                            b.setType(this.getChevronMaterial());
                            
                            // TODO set correct facing direction
                            //Torch t = (Torch) b.getState().getData();
                            //t.setFacingDirection(BlockFace.EAST);
                            //b.getState().setData(t);
                        }
                    } else if (!on && (b.getType().equals(Material.REDSTONE_TORCH_ON) || b.getType().equals(Material.REDSTONE_TORCH_OFF) || b.getType().equals(Material.TORCH))) {
                        b.setType(Material.AIR);
                    }
                }
            }
        }
    }
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Other Overridden methods">
    @Override
    public Block[] getRingBlocks() {
        return ringBlocks;
    }
    
    @Override
    public Block[] getInnerBlocks() {
        return innerBlocks;
    }
    
    @Override
    public Location getExit() {
        return center.getRelative(0, 0, 1);
    }

    @Override
    Block getIrisPowerBlock() {
        return center.getRelative(0, -3, 0).getBlock();
    }
    
    @Override
    public void save(DataOutputStream dos) throws Exception {
        GateLoader.writeLocation(dos, this.center);
    }
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Listeners">
    /**
     * Stops the ring from being broken before the display (Sign)
     * @param event The event that was triggered
     */
    @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
    @Override
    public void onGateStatusChange(GateStatusChangeEvent event) {
        super.onGateStatusChange(event);
        
        if (event.getGate() == this.getGate()) {
            if (event.getNewStatus() == Gate.Status.INCOMING || event.getNewStatus() == Gate.Status.OUTGOING) {
                if (Settings.getInstance().connection.enableVortex() && this.isClear())
                    this.vortex = new Vortex((DirectionSensitiveLocation) this.getExit());
            } else if (event.getNewStatus() == Gate.Status.INACTIVE || 
                    event.getNewStatus() == Gate.Status.DISABLED || 
                    event.getNewStatus() == Gate.Status.BROKEN) {
                if (this.vortex != null && !this.vortex.isFinished()) this.vortex.forceFinish();
                this.vortex = null;
            }
        }
    }
    
    /**
     * Stops the event horizon from moving out of the ring
     * @param event The event that was triggered
     */
    @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
    @Override
    public void onEventHorizonFromTo(BlockFromToEvent event) {
        super.onEventHorizonFromTo(event);
    }
    
    /**
     * Stops the ring from being broken before the display (Sign)
     * @param event The event that was triggered
     */
    @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
    @Override
    public void onRingBroken(BlockBreakEvent event) {
        super.onRingBroken(event);
    }
    
    /**
     * Stops the ring from being broken before the display (Sign)
     * @param event The event that was triggered
     */
    @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
    @Override
    public void onPlayerEnter(PlayerMoveEvent event) {
        super.onPlayerEnter(event);
    }
    
    @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
    @Override
    public void onVehicleEnter(VehicleMoveEvent event) {
        super.onVehicleEnter(event);
    }
    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="Equals and hashcode">
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Ring other = (Ring) obj;
        if (!Objects.equals(this.center, other.center)) {
            return false;
        }
        return true;
    }
    
    @Override
    public int hashCode() {
        int hash = 5;
        hash = 53 * hash + Objects.hashCode(this.center);
        return hash;
    }
    //</editor-fold>
    
}
